package bd403.Backend;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;

/* Singleton DrinkDB class manages the connections to the database. DrinkDB queries a
 * MySQL database via HTTP connection. It returns all drinks from the database as
 * well as adds additional drinks to the database. 
 * Main author: Mike */

public class DrinkDB extends Activity {
	
	protected String URL_START = "http://cubist.cs.washington.edu/~jhl123/index.php?";
	private static DrinkDB instance = null;
	private final String HTTP_DRINK_SUFFIX = "var1=get";
	private final String HTTP_REVIEW_SUFFIX = "var1=getRev";
	private final String HTTP_FAVORITES_SUFFIX = "var1=getFav";
	
	protected DrinkDB() {
		// Singleton pattern, exists to guard against instantiation.
	}
	
	/* Returns an instance of this singleton DrinkDB. If DrinkDB has not yet been
	 * created, a DrinkDB instance is created and returned. Otherwise an instance
	 * of the single DrinkDB is returned.
	 */
	public static DrinkDB getInstance() {
		if (instance == null) {
			instance = new DrinkDB();
		}
		return instance;
	}
	
	/* Returns an Iterator containing all the drinks contained in the database. Returns null
	 * if there is any error establishing connection to the database.
	 */
	public Iterator<Drink> getDrinksFromDB() {
		String drinksString = httpConn(URL_START + HTTP_DRINK_SUFFIX);
		if (drinksString == null) {
			return null;
		}
		String reviewString = httpConn(URL_START + HTTP_REVIEW_SUFFIX);
		if (reviewString == null) {
			return null;
		}
		
		Map<String, ArrayList<Review>> reviewMap = new TreeMap<String, ArrayList<Review>>();
		JSONArray reviewArr;
		try {
			reviewArr = new JSONArray(reviewString);
			JSONObject json_data = null;
			for (int i = 0; i < reviewArr.length(); i++) {
				json_data = reviewArr.getJSONObject(i);
				String name = json_data.get("name").toString();
				Review r = new Review(Integer.parseInt(json_data.get("rating").toString()), json_data.get("comment").toString(), 
						json_data.get("username").toString());

				if (reviewMap.containsKey(name)) {
					reviewMap.get(name).add(r);
				} else {
					ArrayList<Review> newL = new ArrayList<Review>();
					newL.add(r);
					reviewMap.put(name, newL);
				}
			}
		} catch (JSONException e) {
			return null;
		}
		
		JSONArray jArr;
		List<Drink> drinkList = new ArrayList<Drink>();
		try {
			jArr = new JSONArray(drinksString);
			JSONObject json_data = null;
			for (int i = 0; i < jArr.length(); i++) {
				/* Add a Beer, Wine, or Mixed drink to the drinkList */
				json_data = jArr.getJSONObject(i);
				String drinkName = json_data.get("name").toString();
				String drinkType = json_data.get("type").toString().toLowerCase();
				
				Drink d = null;
				if (drinkType.equals("beer")) {
					d = new Beer(drinkName);
				} else if (drinkType.equals("wine")) {
					d = new Wine(drinkName);
				} else if (drinkType.equals("mixed")) {
					d = new Mixed(drinkName);
				} 
				
				if (d != null) {
					if (reviewMap.containsKey(drinkName)) {
						for (Review r : reviewMap.get(drinkName)) {
							d.addRating(r);
						}
					}
					drinkList.add(d);
				}
			}
		} catch (JSONException e) {
			return null;
		}
		
		// Success!
		return drinkList.iterator();
	}
	
	/* Accepts a DrinkParams object that describes a Drink to be added to the database.
	 * Returns true if the drink and review of the drink was successfully added to the database.
	 * Otherwise returns false.
	 */
	public boolean addDrink(DrinkParams dp, String username) {
		// Check if dp is valid
		if (dp == null || username.equals("")) {
			return false;
		}
		
		/* Check to see if the drink is already in the database. If it is, return false.
		 * Otherwise add a new drink and review to the database.
		 */
		if (contains(dp)) {
			throw new IllegalStateException();
		}
		
		/* Get the drink information from newDrinkParams */
		String drinkName = dp.getName().replace(" ", "%20");
		String type = dp.getType();
		String rating = String.valueOf(dp.getRating());
		String comment = dp.getComment().replace(" ", "%20");
		
		ArrayList<NameValuePair> nvPairs = new ArrayList<NameValuePair>();
		InputStream inpStream = null; // input stream to parse JSON response

		try {
			HttpClient httpClient = new DefaultHttpClient();
			String addUrl = URL_START + "var1=getRevAdd&name=" + drinkName + "&username=" + 
													username + "&comment=" + comment + "&rating=" + rating;
			HttpPost httpPost = new HttpPost(addUrl);
			httpPost.setEntity(new UrlEncodedFormEntity(nvPairs));
			HttpResponse result = httpClient.execute(httpPost);
			HttpEntity entity = result.getEntity();
			inpStream = entity.getContent();
		} catch (IOException e) {
			return false;
		}
		
		/* Check if HTTP connection failed */
		if (inpStream == null) {
			return false;
		}
		try {
			HttpClient httpClient = new DefaultHttpClient();
			String addUrl = URL_START + "var1=add&name=" + drinkName +"&type=" + type;
			HttpPost httpPost = new HttpPost(addUrl);
			httpPost.setEntity(new UrlEncodedFormEntity(nvPairs));
			HttpResponse result = httpClient.execute(httpPost);
			HttpEntity entity = result.getEntity();
			inpStream = entity.getContent();
		} catch (IOException e) {
			return false;
		}
		
		/* Check if HTTP connection failed */
		if (inpStream == null) {
			return false;
		}
		
		// Success!
		return true;
	}
	
	/* Checks the database to see if a drink with the name dp.getName() exists.
	 * Returns true if a drink with this name exists. Otherwise returns false.
	 */
	private boolean contains(DrinkParams dp) {
		String drinksString = httpConn(URL_START + HTTP_DRINK_SUFFIX);
		if (drinksString == null) {
			// If there is an Http error, return false to prevent adding something
			// extra to the database.
			return true;
		}
		if(drinksString.equals("null\n"))
			return false;
		JSONArray jArr;
		try {
			jArr = new JSONArray(drinksString);
			JSONObject json_data = null;
			for (int i = 0; i < jArr.length(); i++) {
				json_data = jArr.getJSONObject(i);
				String drinkName = json_data.get("name").toString();
				if (drinkName.equals(dp.getName())) {
					return true;
				}
			}
		} catch (JSONException e) {
			return true;
		}
		return false;
		
	}
	
	/* Accepts a DrinkParams object that describes a review for a drink to be added to the database.
	 * Returns true if the review of the drink was successfully added to the database.
	 * Otherwise returns false.
	 */
	public boolean addReview(DrinkParams dp, String username) {
		/* Check if dp is valid */
		if (dp == null) {
			return false;
		}
		/* Get the drink information from newDrinkParams */
		String drinkName = dp.getName().replace(" ", "%20");
		String rating = String.valueOf(dp.getRating());
		String comment = dp.getComment().replace(" ", "%20");
		
		ArrayList<NameValuePair> nvPairs = new ArrayList<NameValuePair>();
		InputStream inpStream = null; // input stream to parse JSON response

		try {
			HttpClient httpClient = new DefaultHttpClient();

			String addUrl = URL_START + "var1=getRevAdd&name=" + drinkName + "&username=" + username + 
															"&comment=" + comment + "&rating=" + rating;
			HttpPost httpPost = new HttpPost(addUrl);
			httpPost.setEntity(new UrlEncodedFormEntity(nvPairs));
			HttpResponse result = httpClient.execute(httpPost);
			HttpEntity entity = result.getEntity();
			inpStream = entity.getContent();
		} catch (IOException e) {
			return false;
		}
		
		/* Check if HTTP connection failed */
		if (inpStream == null) {
			return false;
		}
		
		// Success!
		return true;
	}
	
	/* Returns a Iterator containing a favorite drink names for userName.
	 */
	public Iterator<String> getFavorite(String userName) {
		String favoriteString = httpConn(URL_START + HTTP_FAVORITES_SUFFIX + "&user=" + userName);
		if (favoriteString == null) {
			return null;
		}
		
		JSONArray jArr;
		List<String> names = new ArrayList<String>();
		try {
			jArr = new JSONArray(favoriteString);
			JSONObject json_data = null;
			for (int i = 0; i < jArr.length(); i++) {
				json_data = jArr.getJSONObject(i);
				names.add(json_data.get("drink_name").toString());
			}
		} catch (JSONException e) {
			return null;
		}
		
		// Success!
		return names.iterator();
	}
	
	/* Returns true if drinkName was added as a favorite drink for userName. Otherwise returns
	 * false.
	 */
	public boolean addFavorite(String userName, String drinkName) {
		if (userName.length() == 0 || drinkName.length() == 0) {
			return false;
		}
		
		/* Get the drink information from newDrinkParams */
		String formatted_drinkName = drinkName.replace(" ", "%20");
		String formatted_userName = userName.replace(" ", "%20");
		
		ArrayList<NameValuePair> nvPairs = new ArrayList<NameValuePair>();
		InputStream inpStream = null; // input stream to parse JSON response
		try {
			HttpClient httpClient = new DefaultHttpClient();
			String addUrl = URL_START + "var1=addFav&user=" + formatted_userName + 
														"&drink=" + formatted_drinkName;
			HttpPost httpPost = new HttpPost(addUrl);
			httpPost.setEntity(new UrlEncodedFormEntity(nvPairs));
			HttpResponse result = httpClient.execute(httpPost);
			HttpEntity entity = result.getEntity();
			inpStream = entity.getContent();
		} catch (IOException e) {
			return false;
		}
		
		/* Check if HTTP connection failed */
		if (inpStream == null) {
			return false;
		}
		
		// Success!
		return true;
	}
	
	/* Accepts a String URL as parameter and creates an HTTP connection with that URL. Returns a
	 * JSON parsed String if a successful connection was made. Otherwise returns null.
	 * Make sure an Internet connection is available before this method is called.
	 */
	protected static String httpConn(String url) {
		ArrayList<NameValuePair> nvPairs = new ArrayList<NameValuePair>();
		InputStream inpStream = null; // input stream to parse JSON response

		try {
			HttpClient httpClient = new DefaultHttpClient();
			HttpPost httpPost = new HttpPost(url);
			httpPost.setEntity(new UrlEncodedFormEntity(nvPairs));
			HttpResponse result = httpClient.execute(httpPost);
			HttpEntity entity = result.getEntity();
			inpStream = entity.getContent();
		} catch (IOException e) {
			return null;
		}
		
		/* Check if HTTP connection failed */
		if (inpStream == null) {
			return null;
		}
		
		String jsonParse = null;
		/* Build up JSON response from the server */
		try {
			BufferedReader rdr = new BufferedReader(new InputStreamReader(inpStream, "iso-8859-1"), 8);
			StringBuilder sb = new StringBuilder();
			sb.append(rdr.readLine() + "\n");

			String line = "0";
			while ((line = rdr.readLine()) != null) {
				sb.append(line + "\n");
			}
			
			/* Close the input Stream and set the results */
			inpStream.close();
			jsonParse = sb.toString();
		} catch (IOException e) {
			/* Return null immediately on any type of failure */
			return null;
		}
		return jsonParse;
	}
}